#=
Upper bound indices J_ub[jj,ii], OO_Ub[ii]: which program's utility gives the UPPER bound on u[jj,ii] or u0[ii], respectively
   -1  => upper bound is Inf (program was first choice, or it was not bound
                   by any info in submitted report because it was off-platform or clearly infeasible.)
   0   => upper bound is outside option (program was not listed, and list did not contain a clearly-feasible program)
   k>0 => ub is utility of program k (program was listed just after k, or (program
                  was not listed AND (list was full OR k was clearly feasible) AND last item on list was k))

Lower bound indices J_lb[jj,ii], OO_lb[ii]: which program's utility gives the LOWER bound on u[jj,ii] or u0[ii], respectively
   -2  => lb is highest feasible u_{ik} such that k is not listed (program is last
                    on list, and either the program is strictly feasible or the list is full)
   -1  => lower bound is -Inf (program was not listed or not constrained)
   0   => lower bound is outside option (program is last on list AND not clearly feasible and list is not full)
   k>0 => lb is utility of program k (program was listed just before k)
=#

function getBoundStructures(MCMC,appsRelevant,appsAll,clearly_infeasible,clearly_feasible,mysample)
    I = length(mysample) #number of guys
    J = Int(MCMC["J"])
    platform = BitArray{1}(vec(MCMC["platform"]))
    J_ub = fill(-1,J,I)  #J_*, OO_* bounds described above
    J_lb = fill(-1,J,I)  #start unconstrained. code below fills in bounds.
    OO_lb = fill(-1,I)
    OO_ub = fill(-1,I)
    waitlistMightCall = fill(false,J,I) #1(j is available to i if and only if i has a good realization of frictions at j).
    A_ub = zeros(Int,J,I) #1(j could have been available to i, given i's app, placement and enrollment).
    A_lb = zeros(Int,J,I)  #1(j must have been available to i, given i's app, placement and enrollment).
    feasibleUnlisted = fill(false,J,I) #programs that could have been listed in restricted app but were not
    #
    for (ii,iold) in enumerate(mysample)
        ei = Int(MCMC["enrollmentIndex"][iold])
        ai = Int(MCMC["placementIndex"][iold])
        rel = appsRelevant[ii,:]
        if ei>0
            A_ub[ei,ii] = 1  #enrolled program must have been available to ii
            A_lb[ei,ii] = 1
            MCMC["platform"][ei]==0 && (waitlistMightCall[ei,ii]=true) #if enrolled program is off platform, it's relevant for alpha
        end
        if ai>0
            A_ub[ai,ii] = 1
            A_lb[ai,ii] = 1
        end
        #all off-platform programs where ii scores "high enough" are relevant for alpha and potentially available
        for jj in findall(x->x.==0,vec(MCMC["platform"]))
            if MCMC["Priorities"][iold,jj]::Float64 > MCMC["wlcutoff"][jj]::Float64
                A_ub[jj,ii] = 1
                waitlistMightCall[jj,ii] = true
            end
        end
        if ai > 0 && (ai in rel)
            placedIndex = findfirst(rel .== ai)
        else
            placedIndex = Inf
        end
        if ei > 0 && (ei in rel)
            enrollIndex = findfirst(rel .== ei)
        else
            enrollIndex = 0
        end
        if enrollIndex > placedIndex #person enrolled in a place ranked below their offer
            println("ii=$ii: enrollIndex > placedIndex")
            rel = [rel[1:enrollIndex-1]; rel[enrollIndex+1:end]; 0]
        end #@assert enrollIndex <= placedIndex
        for (rr,jr) in enumerate(rel) #on-platform programs in relevant subset of application
            if jr==0
                break
            end
            if (rr < placedIndex) && (MCMC["Priorities"][iold,jr] > MCMC["wlcutoff"][jr])
                waitlistMightCall[jr,ii] = true
                A_ub[jr,ii] = (rr >= enrollIndex)
            end
        end
        #now loop through programs to get app constraints
        feas_unlisted = platform .& (.!(clearly_infeasible[ii,:]))
        j1 = rel[1]
        if j1==0 #app list is empty
            OO_ub[ii] = -1 #no upper bound on oo
            OO_lb[ii] = -2 #oo LB is best feasible unlisted thing
            J_ub[findall(feas_unlisted),ii] .= 0 #everything you could have applied but didn't to is worse than oo
            feasibleUnlisted[:,ii] .= feas_unlisted
            continue
        else
            feas_unlisted[j1] = 0 #j1 was listed, hence doesnt belong to feas_unlisted
            J_ub[j1,ii] = -1 #no upper bound for it
            for rr=2:length(rel)+1
                j_above = rel[rr-1]
                j = rr <= length(rel) ? rel[rr] : 0
                if j==0
                    cx = clearly_feasible[ii,j_above] # || (rr > length(rel))
                        #if false: last thing on list > oo > every feasible inside thing not on list
                        #if true: last thing on list > {oo and every feasible inside thing not on list}
                        #   but don't know how oo compares to unlisted inside options.
                        #   by assumption: full list = full relevant set, hence case rr > length(rel) is commented out.
                        #   if we were to assume truthtelling, we would also want cx TRUE in the case rr > length(rel),
                        #   indicating list length constraint may bind.  In practice this does not matter at all b/c so few people (<100) are in this category.
                    J_lb[j_above,ii] = cx ? -2 : 0
                    OO_ub[ii] = j_above
                    OO_lb[ii] = cx ? -1 : -2
                    for j0 in findall(feas_unlisted)
                        J_ub[j0,ii] = cx ? j_above : 0
                    end
                    break
                else
                    feas_unlisted[j] = 0
                    J_ub[j,ii] = j_above
                    J_lb[j_above,ii] = j
                end
            end
            feasibleUnlisted[:,ii] .= feas_unlisted
        end
    end
    return J_lb,J_ub,OO_lb,OO_ub,waitlistMightCall,A_lb,A_ub,feasibleUnlisted
end
